﻿#include "utilityInterfaces.h"

#include <stdarg.h>

#include "utilityBasicFunction.h"
#include "utilityCommunicationManager.hpp"
#include "utilityCommunicator.hpp"
#include "utilityContainer.hpp"
#include "utilityDummyOStream.hpp"
#include "utilityExceptions.h"
#include "utilityMpiWrapper.hpp"
#include "utilityMultiOStream.hpp"
#include "utilityOStream.hpp"
#include "utilityUsingCpp.hpp"

using namespace UTILITY;

void initUtility(int *argc, char ***argv) { COMM::init(argc, argv, "MPI"); }

void getPid(int *pid) { *pid = COMM::getGlobalId(); }

void getCommsize(int *commSize) { *commSize = COMM::getGlobalSize(); }

void allReduceLabels(label *data, const label count, unsigned int op) {
  vector<label> sdata(data, data + count);

  CommData datatype;
  if (sizeof(label) == 4)
    datatype = COMM_INT32_T;
  else if (sizeof(label) == 8)
    datatype = COMM_INT64_T;
  else {
    PERR << " Invalid LABEL type! " << ENDL;
    exit(-1);
  }

  Communicator &gComm = COMM::getGlobalComm();
  gComm.allReduce("all_reduce_labels_", &(sdata[0]), data, count, datatype,
                  (CommOp)op);
  gComm.finishTask("all_reduce_labels_");
}

void allReduceScalars(scalar *data, const label count, unsigned int op) {
  vector<scalar> sdata(data, data + count);

  CommData datatype;
  if (sizeof(scalar) == 4)
    datatype = COMM_FLOAT;
  else if (sizeof(scalar) == 8)
    datatype = COMM_DOUBLE;
  else {
    PERR << " Invalid SCALAR type! " << ENDL;
    exit(-1);
  }

  Communicator &gComm = COMM::getGlobalComm();
  gComm.allReduce("all_reduce_scalars_", &(sdata[0]), data, count, datatype,
                  (CommOp)op);
  gComm.finishTask("all_reduce_scalars_");
}

void bcastLabels(label *data, const label count) {
  Communicator &gComm = COMM::getGlobalComm();
  gComm.bcast("bcast_labels_", data, sizeof(label) * (count));
  gComm.finishTask("bcast_labels_");
}

void bcastScalars(scalar *data, const label count) {
  Communicator &gComm = COMM::getGlobalComm();
  gComm.bcast("bcast_scalars_", data, sizeof(scalar) * (count));
  gComm.finishTask("bcast_scalars_");
}

void gatherLabels(label *sdata, label *rdata, const label count) {
  Communicator &gComm = COMM::getGlobalComm();
  gComm.gather("gather_labels_", sdata, sizeof(label) * (count), rdata,
               sizeof(label) * (count));
  gComm.finishTask("gather_labels_");
}

void gatherScalars(scalar *sdata, scalar *rdata, const label count) {
  Communicator &gComm = COMM::getGlobalComm();
  gComm.gather("gather_scalars_", sdata, sizeof(scalar) * (count), rdata,
               sizeof(scalar) * (count));
  gComm.finishTask("gather_scalars_");
}

void extremeLabelsInProcs(const char *flag, label *data, label *result,
                          const label count) {
  int pid = COMM::getGlobalId();
  int commSize = COMM::getGlobalSize();
  int num = count * COMM::getGlobalSize();
  label *rdata;

  rdata = new label[num];

  MPI_Gather(data, count, COMM_LABEL, rdata, count, COMM_LABEL, 0,
             MPI_COMM_WORLD);
  if (pid == 0) {
    for (int i = 0; i < count; ++i) {
      result[i] = rdata[i * commSize];
      for (int j = 1; j < commSize; ++j) {
        if (strcmp(flag, "MAX") == 0)
          result[i] = MAX(result[i], rdata[i * commSize + j]);
        else if (strcmp(flag, "MIN") == 0)
          result[i] = MIN(result[i], rdata[i * commSize + j]);
        else
          ;
      }
    }
  }
  MPI_Barrier(MPI_COMM_WORLD);
  MPI_Bcast(result, count, COMM_LABEL, 0, MPI_COMM_WORLD);

  DELETE_POINTER(rdata);
}

void extremeScalarsInProcs(const char *flag, scalar *data, scalar *result,
                           const label count) {
  int pid = COMM::getGlobalId();
  int commSize = COMM::getGlobalSize();
  int num = count * COMM::getGlobalSize();
  scalar *rdata;
  rdata = new scalar[num];

  MPI_Gather(data, count, COMM_SCALAR, rdata, count, COMM_SCALAR, 0,
             MPI_COMM_WORLD);
  if (pid == 0) {
    for (int i = 0; i < count; ++i) {
      result[i] = rdata[i * commSize];
      for (int j = 1; j < commSize; ++j) {
        if (strcmp(flag, "MAX") == 0)
          result[i] = MAX(result[i], rdata[i * commSize + j]);
        else if (strcmp(flag, "MIN") == 0)
          result[i] = MIN(result[i], rdata[i * commSize + j]);
        else
          ;
      }
    }
  }
  MPI_Barrier(MPI_COMM_WORLD);
  MPI_Bcast(result, count, COMM_SCALAR, 0, MPI_COMM_WORLD);

  DELETE_POINTER(rdata);
}

void par_std_out(const char *format, ...) {
  va_list args;
  va_start(args, format);

  char pbuffer[4096];
  vsprintf(pbuffer, format, args);

  va_end(args);

  POUT << pbuffer << FLUSH;
}
void par_std_out_(const char *format, ...) {
  va_list args;
  va_start(args, format);

  char pbuffer[4096];
  vsprintf(pbuffer, format, args);

  va_end(args);

  POUT << pbuffer << FLUSH;
}

void proc_std_out(const label pid, const char *format, ...) {
  va_list args;
  va_start(args, format);

  char pbuffer[4096];
  vsprintf(pbuffer, format, args);

  va_end(args);

  SOUT(pid) << pbuffer << FLUSH;
}
void proc_std_out_(const label pid, const char *format, ...) {
  va_list args;
  va_start(args, format);

  char pbuffer[4096];
  vsprintf(pbuffer, format, args);

  va_end(args);

  SOUT(pid) << pbuffer << FLUSH;
}

void master_std_out(const char *format, ...) {
  va_list args;
  va_start(args, format);

  char pbuffer[4096];
  vsprintf(pbuffer, format, args);

  va_end(args);

  COUT << pbuffer << FLUSH;
}
void master_std_out_(const char *format, ...) {
  va_list args;
  va_start(args, format);

  char pbuffer[4096];
  vsprintf(pbuffer, format, args);

  va_end(args);

  COUT << pbuffer << FLUSH;
}
